package com.Touch.tuio {
	
import flash.display.*;
import flash.events.DataEvent;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.geom.Point;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.XMLSocket;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;

//import mx.controls.Alert;

	public class TUIO
	{
		private static var FLOSCSocket:XMLSocket;		
		private static var FLOSCSocketHost:String;			
		private static var FLOSCSocketPort:Number;	
		private static var thestage:Stage;
		
		// 存储了所有可用TUIOObject实例
		private static var objectArray:Array;
		
		// 存储了所有可用TUIOOject对象的ID号,但是在本源文件中并没有用到这个变量
		//private static var idArray:Array;
		

		private static var recordedXML:XML;
		private static var bRecording:Boolean = false;
		//static var xmlPlaybackURL:String = "www/xml/test.xml"; 
		private static var xmlPlaybackURL:String = ""; 
		private static var xmlPlaybackLoader:URLLoader;
		private static var playbackXML:XML;
			
		private static var bInitialized:Boolean = false;
		
	
		// s : 当然舞台，传递文档类的this即可
		// host : 服务器IP地址
		// port : 端口，CCV默认为3000
		// debugXMLFile : 调试用xml文件路径
		// debug : 一个Boolen值，表示当前是否为调试状态（不一定需要调试xml）
		//
		// 这个是TUIO AS 3 Library的初始化函数，在flash工程的文档类开始调用就可以。
		//
		public static function init (s:DisplayObjectContainer, host:String, port:Number):void
		{
			if(bInitialized)
				return;
			FLOSCSocketHost=host;			
			FLOSCSocketPort=port;			
			bInitialized = true;
			thestage = s.stage; // 获取当然文档类的舞台
			
			
			thestage.align = StageAlign.TOP_LEFT;
			thestage.displayState = StageDisplayState.FULL_SCREEN;						
			
			objectArray = new Array();
			//idArray = new Array();
			
			try
			{
				// 与服务端建立xml socket连接，比如CCV。
				// 关于flash的xmlsocket编程大家可以自己去查阅Actionscript 3的文档
				FLOSCSocket = new XMLSocket();
	
				FLOSCSocket.addEventListener(Event.CLOSE, closeHandler);
				FLOSCSocket.addEventListener(Event.CONNECT, connectHandler);
				FLOSCSocket.addEventListener(DataEvent.DATA, dataHandler);
				FLOSCSocket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
				FLOSCSocket.addEventListener(ProgressEvent.PROGRESS, progressHandler);
				FLOSCSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
	
				FLOSCSocket.connect(host, port);			
			
			} catch (e:Error)
			{
			}
			
			// 如果当前是调试状态，设置一些调试所需元件

				recordedXML = <OSCPackets></OSCPackets>;
				bRecording = false;
			
		}

		// 调试xml文件加载完成事件处理函数
		private static function xmlPlaybackLoaded(evt:Event):void {
			playbackXML = new XML(xmlPlaybackLoader.data);
		}
		
		//
		private static function frameUpdate(evt:Event):void
		{
			if(playbackXML && playbackXML.OSCPACKET && playbackXML.OSCPACKET[0])
			{
				processMessage(playbackXML.OSCPACKET[0]);

				delete playbackXML.OSCPACKET[0];
			}
		}		
		
		// 从名字就可以看出来函数功能。根据id来查找所有存储在objectArray数组中的TUIOObject实例
		public static function getObjectById(id:Number): TUIOObject
		{
			for(var i:int=0; i<objectArray.length; i++)
			{
				if(objectArray[i].ID == id)
				{
					//trace("found " + id);
					return objectArray[i];
				}
			}
			//trace("Notfound");
			
			return null;
		}
		
		// 这个函数要结合TUIOObject类来理解。
		public static function listenForObject(id:Number, reciever:Object):void
		{
			var tmpObj:TUIOObject = getObjectById(id);
			
			if(tmpObj)
			{
				tmpObj.addListener(reciever);				
			}

		}
		
		// 这个是最重要的数据处理函数
		public static function processMessage(msg:XML):void
		{

			var fseq:String;
			var node:XML;
			
			trace(msg);
			
			// 处理fseq消息，格式如下：
			//
			// /tuio/[profileName] fseq int32
			// profileName 代表定义好的常用可感知用户界面配置
			// fseq 消息标示
			// int32 一个类型为int32的fseq值
			for each(node in msg.MESSAGE)
			{
				if(node.ARGUMENT[0] && node.ARGUMENT[0].@VALUE == "fseq")
					fseq = node.ARGUMENT[1].@VALUE;					
			}
///			trace("fseq = " + fseq);

			// 处理alive消息，格式如下：
			//
			// uio/[profileName] alive [list of active sessionIDs]
			// profileName 代表定义好的常用可感知用户界面配置
			// alive 消息标示
			// [list of active sessionIDs] 一系列当前有用的目标对象的id号
			
			for each(node in msg.MESSAGE)
			{
				if(node.ARGUMENT[0] && node.ARGUMENT[0].@VALUE == "alive")
				{
					// 重置objectArray数组中存储的所有TUIOOject实例的isAlive属性
					for each (var obj1:TUIOObject in objectArray)
					{
						obj1.isAlive = false;
					}
					
					// 在这个地方重新定义了一个array，但是是无用的变量。
					//var newIdArray:Array = new Array();					
					
					// 循环获得alive消息所有id
					//
					// alive消息是配合set消息来确定哪些blobs点是有用，也就是哪些set消息中包含的数据是有效的
					// 可以结合CCV中的源码来理解
					for each(var aliveItem:XML in node.ARGUMENT.(@VALUE != "alive"))
					{
						// 根据alive消息中的id，相应的TUIOObject实例的isAlive属性设置为true，也即是当前点可用
						if(getObjectById(aliveItem.@VALUE))
							getObjectById(aliveItem.@VALUE).isAlive = true;

					}   
					
					//trace(idArray);
					//idArray = newIdArray;
				}

			}			
			
			// 处理set消息				
			for each(node in msg.MESSAGE)
			{
				if(node.ARGUMENT[0])
				{
					var type:String;
					
					// 在这里要注意区别2Dobj和2Dcur这两种profile的区别,结构如下，具体请阅读TUIO Protocol Specification
					//
					// /tuio/2Dobj set s i x y a X Y A m r
                    // /tuio/2Dcur set s x y X Y m
					//
					// 由于本人结合研究的CCV发送的是/tuio/2Dcur消息结构，所以我先注释/tuio/2Dcur结构消息处理
					// 对于/tuio/2Dobj消息处理的注释在以后补上。
					
					if(node.@NAME == "/tuio/2Dobj")
					{
						type = node.ARGUMENT[0].@VALUE;				
						if(type == "set")
						{
							var sID:int = node.ARGUMENT[1].@VALUE;
							var id:int = node.ARGUMENT[2].@VALUE;
							var x:Number = Number(node.ARGUMENT[3].@VALUE) * thestage.stageWidth;
							var y:Number = Number(node.ARGUMENT[4].@VALUE) * thestage.stageHeight;
							var a:Number = Number(node.ARGUMENT[5].@VALUE);
							var X:Number = Number(node.ARGUMENT[6].@VALUE);
							var Y:Number = Number(node.ARGUMENT[7].@VALUE);
							var A:Number = Number(node.ARGUMENT[8].@VALUE);
							var m :int= node.ARGUMENT[9].@VALUE;
							//var r:int = node.ARGUMENT[10].@VALUE;
							
							// send object update event..
							
							var objArray:Array = thestage.getObjectsUnderPoint(new Point(x, y));
							var stagePoint:Point = new Point(x,y);					
							var displayObjArray:Array = thestage.getObjectsUnderPoint(stagePoint);							
							var dobj:DisplayObject = null;
							
//							if(displayObjArray.length > 0)								
//								dobj = displayObjArray[displayObjArray.length-1];										

							var tuioobj:TUIOObject = getObjectById(id);
							if(tuioobj == null)
							{
								tuioobj = new TUIOObject("2Dobj", id, x, y, X, Y, sID, a, dobj);
								thestage.addChild(tuioobj.spr);
								
								objectArray.push(tuioobj);
								tuioobj.notifyCreated();								
							} else {
								tuioobj.spr.x = x;
								tuioobj.spr.y = y;								
								tuioobj.x = x;
								tuioobj.y = y;
								tuioobj.dX = X;
								tuioobj.dY = Y;
								
								tuioobj.setObjOver(dobj);
								tuioobj.notifyMoved();								
							}
							
							try
							{
								if(tuioobj.obj && tuioobj.obj.parent)
								{							
									
									var localPoint:Point = tuioobj.obj.parent.globalToLocal(stagePoint);							
									tuioobj.obj.dispatchEvent(new TUIOEvent(TUIOEvent.TUIO_MOVE, true, false, x, y, localPoint.x, localPoint.y, tuioobj.oldX, tuioobj.oldY, tuioobj.obj, false,false,false, true, m, "2Dobj", id, sID, a));
								}
							} catch (e:Error)
							{
							}		
						}	
					}
					//
					// /tuio/2Dcur set s x y X Y m
					//
					else if(node.@NAME == "/tuio/2Dcur")
					{
//						trace("2dcur");
						type = node.ARGUMENT[0].@VALUE;				
						
						// 判断消息类型是否为set
						if(type == "set") 
						{
							// 当前点的id
							var id_2:int = node.ARGUMENT[1].@VALUE;
							
							// 将x,y分别乘以舞台宽度和高度转化为当前点全局舞台坐标中的水平和垂直坐标
							// 注意在这里为什么是分别对应乘以舞台宽度和高度，需要认真理解
							var x_2:Number = Number(node.ARGUMENT[2].@VALUE) * thestage.stageWidth;
							var y_2:Number = Number(node.ARGUMENT[3].@VALUE) * thestage.stageHeight;
							
							// 分别是当前点的位移量
							var X_2:Number = Number(node.ARGUMENT[4].@VALUE);
							var Y_2:Number = Number(node.ARGUMENT[5].@VALUE);
							
							// 这个值根据TUIO Protocol Specification的解释和CCV中源码实现，我的理解产生了矛盾。
							// 需要我后续的实验验证。故再次不做注释说明，也希望那位与我共同讨论（leezhm@126.com）
							var m_2:int = node.ARGUMENT[6].@VALUE;
							//var area = node.ARGUMENT[7].@VALUE;							
							
							var stagePoint_2:Point = new Point(x_2, y_2);	
							
							// 下面代码段是一个理解重点
							// 根据当前点坐标得到舞台上该点下面所有显示对象。关于getObjectsUnderPoint函数
						    // 可以查阅Actionscript 3的Docs，它返回的包含所有对象的一个数组
							var displayObjArray_2:Array = thestage.getObjectsUnderPoint(stagePoint_2);
							var dobj_2:DisplayObject = null;
							
							// 根据返回的数组，得到最上层的显示对象
							//
							// 为什么要得到这个最上层的显示对象呢？怎么实现触摸响应的呢?
							// 比如：舞台上有一个MovieClip，在这个MC上有一个Button。
							// 当你的手触摸到这个button的时候，我们得到了当前点坐标（肯定是stageX和stageY），这样我们
							// 用上面的方法获得这个点下面所有的显示对象，包括button，MovieClip等。这个时候我们就要获取
							// 最上层这个button，然后让这个button发送一个TUIOEvent.TUIO_DOWN等这样的事件。然后我们只需
							// 要在我们的flash中的button上监听这个TUIO_DOWN事件就可以了。
							//
							// 理解了么？这就是由屏幕上一个点，到flash舞台上元素响应事件的原理。好好学习下Actionscript 3
							// 的事件机制后理解这些吧。因为这个让我也想起了Box2D这个库中实现鼠标事件的原理。
							if(displayObjArray_2.length > 0)								
								dobj_2 = displayObjArray_2[displayObjArray_2.length-1];							
														
								
							//var sztmp:String="";
//							for(var i=0; i<displayObjArray.length; i++)
//								sztmp += (displayObjArray[i] is InteractiveObject) + ",";
//							trace(sztmp);

							// 根据当前id号在objectArray数组中查找是否存在对于的TUIOOjbect实例
							var tuioobj_2:TUIOObject = getObjectById(id_2);
							if(tuioobj_2 == null)
							{
								// 不存在，则创建响应TUIOObject对象，并加入到舞台和数组中(个人觉得没必要加载到舞台，
								// 况且为了加载还重新定义了一个Sprite，真是有点多此一举)
								//
								// 注意这里创建TUIOObject实例所传递的参数中最后一个参数。当然这也要结合TUIOObject类
								// 来理解。具体的将会在TUIOObject类中注释。
								//trace()
								tuioobj_2 = new TUIOObject("2Dcur", id_2, x_2, y_2, X_2, Y_2, -1, 0, dobj_2);
								//tuioobj.area = area;
								
								//测试用
								//thestage.addChild(tuioobj.spr);								
								objectArray.push(tuioobj_2);
								
								// 发送TUIO_OVER和TUIO_DOWN消息（请结合TUIOObject代码来理解）
								tuioobj_2.notifyCreated();
							} 
							else 
							{
								// 存在，则更新当前点的信息
								
								//测试用
								//tuioobj.spr.x = x;
								//tuioobj.spr.y = y;
								tuioobj_2.x = x_2;
								tuioobj_2.y = y_2;
								//tuioobj.area = area;								
								tuioobj_2.dX = X_2;
								tuioobj_2.dY = Y_2;
								
								tuioobj_2.setObjOver(dobj_2);
								tuioobj_2.notifyMoved();
							}  

							try
							{
								// 发送TUIO_MOVE消息
								if(tuioobj_2.obj && tuioobj_2.obj.parent)
								{							
									var localPoint_2:Point = tuioobj_2.obj.parent.globalToLocal(stagePoint_2);							
									tuioobj_2.obj.dispatchEvent(new TUIOEvent(TUIOEvent.TUIO_MOVE, true, false, x_2, y_2, localPoint_2.x, localPoint_2.y, tuioobj_2.oldX, tuioobj_2.oldY, tuioobj_2.obj, false,false,false, true, m_2, "2Dobj", id_2, sID, a));
								}
							} catch (e:Error)
							{
								//trace("Dispatch event failed " + tuioobj.name);
							}

	
						}
					}
				}
			}
			

			
			for (var i:int=0; i<objectArray.length; i++ )
			{
				// 如果当前点非alive，则总objectArray数组中去掉，并且清楚当前点。
				if(objectArray[i].isAlive == false)
				{
					objectArray[i].kill();
					
					//测试用
					//thestage.removeChild(objectArray[i].spr);
					objectArray.splice(i, 1);
					i--;
				} 
				else
				{
					
				}
			}
		}
		

		

		
        private static function closeHandler(event:Event):void {
            //trace("closeHandler: " + event);
        }

        private static function connectHandler(event:Event):void {
         //   trace("connectHandler: " + event);
        }

        private static function dataHandler(event:DataEvent):void {
			
            //trace("dataHandler: " + event);
			
			if(bRecording)
				recordedXML.appendChild( XML(event.data) );
			
			processMessage(XML(event.data));
        }

		//链接失败信息
        private static function ioErrorHandler(event:IOErrorEvent):void {
//			thestage.tfDebug.appendText("ioError: " + event + "/n");	
			//Alert.show("没有连接到TUIO设备或TUIO服务！请检查设备或程序！","Error");
           // trace("ioErrorHandler: " + event);
        }

        private static function progressHandler(event:ProgressEvent):void {
            //trace("progressHandler loaded:" + event.bytesLoaded + " total: " + event.bytesTotal);
        }

        private static function securityErrorHandler(event:SecurityErrorEvent):void {
            trace("securityErrorHandler: " + event);
//			thestage.tfDebug.appendText("securityError: " + event + "/n");			
        }
	}
}